Skip to content

feat: Add A2A parser plugin for inbound request inspection#363

Merged
huang195 merged 4 commits into
rossoctl:mainfrom
huang195:feat/a2a-parser-plugin
May 5, 2026
Merged

feat: Add A2A parser plugin for inbound request inspection#363
huang195 merged 4 commits into
rossoctl:mainfrom
huang195:feat/a2a-parser-plugin

Conversation

@huang195

@huang195 huang195 commented May 4, 2026

Copy link
Copy Markdown
Member

Summary

  • Adds a2a-parser pipeline plugin that parses inbound A2A JSON-RPC 2.0 requests (message/send, message/stream) and populates pctx.Extensions.A2A for downstream policy plugins (e.g., guardrails)
  • Expands A2AExtension type with RPCID, SessionID, MessageID, Role fields to match the actual A2A protocol payload from Kagenti UI
  • Renames A2APart.Type to Kind to align with A2A protocol field naming
  • Registers a2a-parser in the plugin registry (opt-in via config, not added to defaults)

Design

Follows the exact same pattern as mcp-parser (PR #361):

  • Declares BodyAccess: true and Writes: ["a2a"]
  • Always returns Continue (never rejects -- parsing only)
  • Gracefully handles nil/empty body, invalid JSON, unknown methods
  • Reuses jsonRPCRequest helper from the same package

Pipeline placement

pipeline:
  inbound:
    plugins:
      - jwt-validation
      - a2a-parser        # this PR
      # future: - guardrails (reads "a2a" extension slot)

Debug logging

Comprehensive slog.Debug calls at every parsing step for early debugging.
Enable with LOG_LEVEL=debug in authbridge-config ConfigMap.

Test plan

  • 11 unit tests covering: capabilities, message/send, message/stream, multiple parts, file parts, unknown methods, nil/empty/invalid body, missing message, OnResponse no-op
  • go vet ./... passes
  • go test ./... passes (all packages including existing MCP/JWT/token-exchange tests)
  • go build ./... passes for cmd/authbridge
  • End-to-end: add a2a-parser to inbound pipeline config in Kind, send chat from UI, verify a2a-parser: parsed message/stream in logs

Assisted-By: Claude (Anthropic AI) noreply@anthropic.com

huang195 added 2 commits May 4, 2026 12:05
Parses A2A JSON-RPC 2.0 request bodies (message/send, message/stream)
and populates pctx.Extensions.A2A with session, message, role, and parts
for downstream policy plugins (e.g., guardrails).

- Expand A2AExtension with RPCID, SessionID, MessageID, Role fields
- Rename A2APart.Type to Kind to match A2A protocol field name
- Register a2a-parser in the plugin registry
- Add comprehensive debug logging for LOG_LEVEL=debug troubleshooting

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Hai Huang <huang195@gmail.com>
- Make message extraction generic: extract from params.message
  regardless of method name (forward-compatible with future A2A methods)
- Add uri fallback for file parts (A2A spec allows uri as alternative
  to data)
- Consolidate debug logging: one summary log instead of per-field calls
- Rename test to AnyMethod, add FutureMethodWithMessage test
- Add DataPart test (exercises json.Marshal path)
- Add FilePartURI test (exercises uri fallback)

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Hai Huang <huang195@gmail.com>
Comment on lines +88 to +92
case "file":
content, _ = partMap["data"].(string)
if content == "" {
content, _ = partMap["uri"].(string)
}

@evaline-ju evaline-ju May 4, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ref https://a2a-protocol.org/latest/definitions/#protobuf I'm curious how this case maps. Does another part of Kagenti form file data?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. Looking at the A2A spec, the current spec (June 2025 draft) uses a unified Part with oneof content: text, raw (base64 bytes), url (file pointer), or data (structured JSON), plus optional mediaType/filename metadata.

The older SDK samples (which Kagenti UI currently uses) still send {"kind": "file", "data": "..."} or {"kind": "file", "uri": "..."} — that is what this code handles today. No part of Kagenti currently creates file parts; this branch just ensures we do not lose content if a future A2A client sends one.

As the spec stabilizes, we should update this to match the canonical Part structure (no kind discriminator, use mediaType + content field presence instead). I will add a TODO comment noting this.

Comment thread authbridge/authlib/plugins/a2aparser.go Outdated
huang195 added a commit to huang195/kagenti-extensions that referenced this pull request May 4, 2026
Dead code in this PR -- no callers remain after removing the method
switch. The A2A parser (PR rossoctl#363) will re-add them when it merges.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Hai Huang <huang195@gmail.com>
Malformed parts (non-string kind, empty kind) are now silently skipped
instead of producing empty A2APart entries. Hardens the parser against
non-compliant or malicious payloads.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Hai Huang <huang195@gmail.com>
Comment thread authbridge/authlib/plugins/a2aparser.go
Add test for type-mismatched part content (text: false, data: 0,
uri: {}). Skip nil data values in the data-part branch to avoid
serializing JSON null as content. Add TODO noting spec evolution.

Signed-off-by: Hai Huang <hai@us.ibm.com>
Signed-off-by: Hai Huang <huang195@gmail.com>

@mrsabath mrsabath left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean A2A parser plugin — mirrors MCP parser pattern, comprehensive tests (14 cases), all prior review feedback addressed. LGTM.

content, _ = partMap["uri"].(string)
}
case "data":
if dataVal, ok := partMap["data"]; ok && dataVal != nil {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: This logs at Info level on every successfully parsed request. Since the parser runs on all inbound traffic (not just A2A), this could be noisy in production. Consider slog.Debug here and keeping only the existing Debug log below for detailed fields. The MCP parser uses slog.Info too, so this is consistent — but both might want to be Debug in the long run.

@huang195
huang195 merged commit 4215fa6 into rossoctl:main May 5, 2026
17 checks passed
@huang195
huang195 deleted the feat/a2a-parser-plugin branch May 5, 2026 00:48
huang195 added a commit to huang195/kagenti-extensions that referenced this pull request May 5, 2026
Dead code in this PR -- no callers remain after removing the method
switch. The A2A parser (PR rossoctl#363) will re-add them when it merges.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Hai Huang <huang195@gmail.com>
huang195 added a commit to huang195/kagenti-extensions that referenced this pull request May 5, 2026
- Make message extraction generic: extract from params.message
  regardless of method name (forward-compatible with future A2A methods)
- Add uri fallback for file parts (A2A spec allows uri as alternative
  to data)
- Consolidate debug logging: one summary log instead of per-field calls
- Rename test to AnyMethod, add FutureMethodWithMessage test
- Add DataPart test (exercises json.Marshal path)
- Add FilePartURI test (exercises uri fallback)

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Hai Huang <huang195@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants